
package Cluedo.AI;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.StringTokenizer;
import java.util.Vector;

import com.cluedo.CluedoDataPad;

import Cluedo.API.CluedoConfig;
import Cluedo.API.CluedoListener;
import Cluedo.API.DisproofAlert;
import Cluedo.API.Message;
import Cluedo.API.SuggestionAlert;
import Cluedo.Controller.Controller;
import Cluedo.Game.Card;
import Cluedo.Game.CardCollection;
import Cluedo.Game.Map;
import Cluedo.Game.Player;
import Cluedo.Game.Position;
import Cluedo.Game.Room;
import Cluedo.Game.Square;
import Cluedo.Game.StartingSquare;

/**
 * AI interacts with the game by listening to messages from the GameController and then sending
 * messages back to make changes.
 * <p>
 * 
 * First Created on Dec 12, 2004
 * 
 * @author Alex Ellis
 * 
 */
public class SmartSimpleAI implements
        Runnable, Cluedo.API.AIContract {

    static int sleepPeriod = 10;

    CluedoDataPad cDataPad = null;

    HashMap<String, Integer> playerMapping;

    Player me;

    /**
     * This identifies the AI, so it can decide when it's his turn to respond to input.
     */
    String myName = "";

    /**
     * If set to true the AI's run() function will end.
     */
    boolean dead = false;

    /**
     * Stores all incoming messages in a queue. This AI is a thread, therefore it receives messages
     * into a queue and parses them from its run() function.
     */
    Vector inbox = new Vector();

    /**
     * Current behaviour of the AI
     */
    String motive = "";
    /**
     * The room the AI wants to get to, intermediate Squares aren't stored here.
     */
    Position destination = null;

    /**
     * Detective if we were moved by a suggest
     * 
     */
    Position lastPos = null;

    /**
     * Indicates a disproof to the AI's suggestion was received.
     */
    boolean disproofReceived = true;

    /**
     * Used to store a disproof to the AI's suggestion.
     */
    Message incomingDisproof = null;

    /**
     * List of motives that an AI may take.
     */
    String[] motives = {
            "findRoom", "walkToRoom",
    };

    /**
     * CluedoListener acts as an inbox to receive messages from the GameController and will decide
     * whether it needs to update any information.
     *
     */
    CluedoListener cluedoListener = new CluedoListener() {
        /**
         * Messages here are received from the TextHandler and result in some action being taken
         * locally in this View, commands called from here are located in an inner-class called
         * LocalEvents.<br>
         * The return value from the local event is outputted to the user.
         *
         */
        public void localMessage(Cluedo.API.Message message) {
        }

        /**
         * Receives messages from the GameController and handles them in GameEvents
         *
         */
        public void notifyAlert(Message message) {
            if (debugLevel >= 2) {
                outputMessage("[SmartSimpleAI:notifyAlert] [" + message.type + "] '" + message.data
                        + "'\n");
            }
            // One case where we need a little help..
            if (disproofReceived == false
                    && message.type.equals("disproofAlert")) {
                disproofReceived = true;
                incomingDisproof = message;
                inbox.add(message);
            }
            else {
                inbox.add(message);
            }
        }

        /*
         * End code that handles the interaction of the GUI
         */
        public CluedoListener getCluedoListener() {
            return this;
        }

    };

    public CluedoListener getCluedoListener() {
        return cluedoListener;
    }

    private void sleepFor(int interval) {
        try {
            Thread.sleep(interval);
            outputMessage("\nPausing execution.. (" + interval + ") ms\n");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Construct a new simple AI, and bind itself to the Controller to receive game messages.
     *
     * @param args Does nothing
     * @param gameController2 A pointer/link to the Controller.
     * @param AIName Name that uniquely identifes this AI player
     */
    public SmartSimpleAI(String args, Controller gameController, String AIName) {
        // super(args);
        myName = AIName;
        this.gameController = gameController;
        System.out.println("[SmartSimpleAI:] GC=" + this.gameController.toString() + ", NAME="
                + myName);
        bindAI();
        motive = "findRoom";
    }

    /**
     * AI will bind its CluedoListener to the gameController such that it will receive game
     * messages.
     */
    public void bindAI() {
        System.out.println("[SmartSimpleAI2:BindAI] binding to listener " + getCluedoListener());
        try {
            gameController.bindListener(getCluedoListener());
        } catch (Exception Error) {
            System.out.println("[SmartSimpleAI2:BindAI] can't bind");
        }
    }

    /**
     * If called thread will exit run() method. Called when game ends.
     */
    synchronized public void die() {
        dead = true;
    }

    /**
     * Main AI loop, will stop if die() is called.
     *
     * Removes message from inbox and processes it in handleMessage
     *
     */
    public void run() {
        while (dead == false) {
            try {
                sleepFor(100);

                if (inbox.size() > 0) {
                    while (inbox.size() > 0) {
                        Thread.sleep(10);
                        try {
                            handleMessage(((Message) inbox.firstElement()));
                        } catch (Exception unknown) {
                            unknown.printStackTrace();
                        }
                        inbox.remove(inbox.firstElement());
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    boolean printedMyCards = false;

    /**
     * A message is taken from the queue and given to the AI, he decides what to do about that.
     * 
     * @param message Message from the Q.
     *
     */
    public synchronized void handleMessage(Message message) {
        String output = "";

        if (printedMyCards == false) {
            System.out.println(myName + "\n"
                    + gameController.getBoard().getPlayer(myName).getCards().toXML());
            printedMyCards = true;
        }

        if (debugLevel > 5)
            outputMessage("handleMessage " + message.type);
        if (gameController.getBoard().getGameFinished() == false) {

            if (message.type.equals("newRoll")) {

            }
            if (message.type.equals("commenceGame")) {
                message.type = "nextPlayer";
                outputMessage("We're all waiting a little bit for the game code to warm-up");
                sleepFor(sleepPeriod * 10);

            }
            else if (message.type.equals("newEnvelope")) {

            }
            else if (message.type.equals("suggestionAlert")
                    && message.data.equals(myName)) {
                handleSuggestionAlert(message);
            }
            else if (message.type.equals("disproofAlert")) {
                outputMessage("\nHeard a disproofAlert ...\n");
                disproofReceived = true;
            }
            else if (message.type.equals("winGame")) {
                outputMessage("Game ends.", 1);
                dead = true;
                return;
            }
            else if (message.type.equals("gameOver")) {
                outputMessage("Game ends.", 1);
                dead = true;
                return;
            }
            if (message.type.equals("nextPlayer")) {

                if (gameController.getBoard().getCurrentPlayer().getRealName().equals(myName)) {
                    // outputMessage(buildCards());
                    outputMessage(" ********************************************");
                    outputMessage(" **** "
                            + gameController.getBoard().getCurrentPlayer().getRealName() + " ****");
                    outputMessage(" ********************************************");
                    output = "\n";

                    try {
                        takeTurn();
                    } catch (Exception exp) {
                        exp.printStackTrace();
                    }
                    // outputMessage(buildCards());
                }
            }
            else if (message.type.equals("moveAlert")) {

            }
        }
        else if (dead == false) {
            outputMessage("Game is over and i'm quitting..");
            die();
        }
        if (!output.equals("")) {
            outputMessage(output);
        }
    }

    /**
     * @param string
     * @param i
     */
    private void outputMessage(String output, int i) {
        if (i == 1) {
            StringTokenizer lines = new StringTokenizer(output, "\n");
            while (lines.hasMoreElements() == true) {
                System.err.println("[AI:" + myName + "] " + lines.nextToken());
            }
            System.err.println();
        }
    }

    /**
     * Manually set the AI name, see constructor
     */
    public void setMyName(String myName) {
        this.myName = myName;
    }

    /**
     * Only prints a debug
     */
    public void initaliseAI() {
        System.out.println("[SmartSimpleAI2:initaliseAI] Initalised " + myName + ".");
    }

    /**
     * Start code that handles the interaction of the AI
     * 
     */

    /** The game controller for this View */
    Controller gameController = null;
    /** The parser for text input from this View */
    Cluedo.GUI.TextParser textParser;
    /** The handler for input from the parser on this View */
    Cluedo.GUI.TextEventHandler textHandler = new Cluedo.GUI.TextEventHandler(this);
    /** Create a game config class to read in and sort out what the new game will be like */
    CluedoConfig gameConfig = null;
    int debugLevel = 0;

    /**
     * Debug function makes clear which AI is "talking"
     */
    synchronized void outputMessage(String output) {
        StringTokenizer lines = new StringTokenizer(output, "\n");
        while (lines.hasMoreElements() == true) {
            System.out.println("[AI:" + myName + "] " + lines.nextToken());
        }
        System.out.println();
    }

    /**
     * Figures out what to do with a message of type ='suggestionAlert'.
     * <p>
     *
     * Searches cards in suggestionAlert until it finds one it has in its set of cards, then
     * composes disproofAlert alert as a reply to the message and relays this through the
     * controller.
     *
     */
    private void handleSuggestionAlert(Message message) {

        outputMessage("\nHeard a suggestionAlert directed at me...\n");
        Message disproofAlert = new Message();
        String chosenCard = "";

        disproofAlert.type = "disproofAlert";

        SuggestionAlert alert = (SuggestionAlert) message.parameters.elementAt(0);
        Player me = gameController.getBoard().getPlayer(myName);

        if (me.getCards().containsCard(alert.getWhat()))
            chosenCard = alert.what.getName();
        else if (me.getCards().containsCard(alert.getWhere()))
            chosenCard = alert.where.getName();
        else if (me.getCards().containsCard(alert.getWho()))
            chosenCard = alert.who.getName();

        // If this happens, there is a bug in the system.
        if (chosenCard.equals(""))
            outputMessage("I didn't have a card to disprove with and yet I was asked to disprove!");

        // DisproofAlert dalert = new DisproofAlert(chosenCard, alert);
        DisproofAlert dalert = new DisproofAlert(chosenCard, alert, myName);
        disproofAlert.parameters.add(dalert);

        gameController.relayMessage(disproofAlert);

        outputMessage("\nRelay of disproofAlert... Ok\n");

    }

    /**
     * Clean up anything and notify gameController to allow next player to play.
     *
     */
    synchronized public String endGo() throws Exception {
        String output = "";
        Vector empty = new Vector();

        gameController.directCommand("nextPlayer", empty);

        return output;
    }

    /**
     * Function for debug, returns string status of all cards.
     *
     * @return String containing status of all cards.
     */
    synchronized public String buildCards() {
        StringBuffer buffer = new StringBuffer();

        Player me = gameController.getBoard().getPlayer(myName);
        CardCollection myDCards = me.getDetectivePad().getCardList();

        for (int i = 0; i < myDCards.countCards(); i++) {
            buffer.append("- " + myDCards.cardAt(i).getName() + "\t"
                    + myDCards.cardAt(i).isEliminated() + "\n");
        }

        return buffer.toString();
    }

    /**
     * Called if after a nextPlayer alert if getBoard().getCurrentPlayer().getRealName() == me
     * <p>
     *
     * AI player will decide what he needs to do on this turn.
     *
     */
    synchronized public void takeTurn() throws Exception {
        String output = "";
        boolean deadlock = inDeadlock();

        outputMessage("takeTurn..");

        /** Firstly make a small thinking pause */
        sleepFor(sleepPeriod);

        /** Get the bits we need for the turn. */
        Player me = gameController.getBoard().getPlayer(myName);
        CardCollection myDCards = me.getDetectivePad().getCardList();

        System.out.println("\t\tinDeadlock() " + inDeadlock());

        /**
         * Check if an accusation should be made.
         *
         */
        if (shouldAccuse() == true) {
            boolean wonGame = makeAccusation();

            if (wonGame == true) {
                Vector arguments = new Vector();
                arguments.add(me);
                gameController.directCommand("winGame", arguments);
            }
            else {
                gameController.directCommand("removeInteractive", null);
            }

            System.out.println("My accusation was " + wonGame);

            dead = true;
            return;
        }

        output += "I have to eliminate " + myDCards.getUneliminatedCards().size() + " / ("
                + myDCards.countCards() + ") cards.\n";

        outputMessage(output);

        /**
         * If we get moved on the last turn then we have to find the nearest room again
         */
        /*
         * if(lastPos != null && (motive.equals("walkToRoom") && lastPos != me.getPosition())) {
         * motive = "findRoom"; System.out.println("I got moved last turn."); }
         */

        /**
         * If not in the process of moving to a room, find a room that we want to visit.
         */
        if (motive.equals("findRoom")) {
            destination = findRoom();
            System.out.println("Finding room: " + destination);
            motive = "walkToRoom";
        }

        sleepFor(sleepPeriod);

        /**
         * If I'm supposed to find my way to the room then I'll start doing that.
         * 
         */
        if (deadlock == false) {
            if (motive.equals("walkToRoom")) {
                if (destination == null || destination.equals(me.getPosition())) {
                    System.out.println("This current pos is the nearest!?");
                    destination = findFreeNeighbour(me.getPosition());
                }
                if (destination != null) {
                    walkToRoom(destination);
                }
                else {
                    motive = "findRoom";
                }
            }
        }

        sleepFor(sleepPeriod);

        /**
         * If it's possible to make a suggestion after the turn, then do so.
         *
         */
        if (shouldSuggest() == true) {
            output += suggest();
        }

        if (destination == me.getPosition()) {
            outputMessage("Arrvied at destination");
            motive = "findRoom";

        }
        else {
            outputMessage("Still in transit");
        }

        // lastPos = me.getPosition();

        /**
         * Now end the go and let the next player have his go.
         */
        try {
            outputMessage("calling endGo()");
            endGo();
            outputMessage("endGo() returned");
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    /**
     * Decides if the AI will make a suggestion where it is.
     *
     * Added mainly to disable conspiracy.
     * 
     * @return true if ok, false if not ok
     */
    public boolean shouldSuggest() {
        boolean ok = true;
        Player me = gameController.getBoard().getCurrentPlayer();
        Position p = me.getPosition();

        if (me.getCards().containsCard(new Card(p.getID(), "Room")) == true)
            ok = false;

        if (me.canSuggestHere() == false || me.canSuggest() == false)
            ok = false;

        return ok;
    }

    /**
     * Check for a free position in one of the neighbouring nodes.
     *
     * @return null if node doesn't have free neighbouring node, otherwise return first found node
     */
    Position findFreeNeighbour(Position node) {
        Position found = null;

        for (int i = 0; i < node.getNeighbours().size(); i++) {
            if (((node.getNeighbours().elementAt(i)) instanceof Room == true) ||
                    ((Position) node.getNeighbours().elementAt(i)).isOccupied() == false) {
                found = (Position) node.getNeighbours().elementAt(i);
            }
        }

        return found;
    }

    /**
     * Walk to destination.
     * <p>
     * 
     * Try to get to the destination by whatever means neccessary, make suggestions along the way
     * wherever permitted.
     * 
     */
    synchronized public void walkToRoom(Position destination) {
        try {
            Player me = gameController.getBoard().getPlayer(myName);

            System.out.println("walkToRoom(" + destination.getID() + ") at: "
                    + me.getPosition().getID());

            // System.out.println("Before dice");

            int diceRoll = 0;

            /**
             * Call the roll-dice function to find out how many squares we can move
             */
            try {
                diceRoll = rollDice();
            } catch (Exception e) {
                e.printStackTrace();
            }

            // System.out.println("After dice");
            sleepFor(sleepPeriod);

            System.out.println("+ Dice: " + diceRoll);

            /**
             * Check if there is a legal path to the route
             */
            Vector legalRoute = gameController.getBoard().map
                    .routeTo(me.getPosition(), destination);

            Vector illegalRoute = gameController.getBoard().map.illegalRouteTo(me.getPosition(),
                    destination);

            Vector chosenRoute = new Vector();

            boolean wholePath = false;
            int roll = me.getMovesRemaining();

            int movesOnPath = 0;

            outputMessage("Illegal size: "
                    + gameController.getBoard().map.illegalRouteTo(me.getPosition(), destination)
                            .size() + "\n");
            outputMessage("Legal size: "
                    + gameController.getBoard().map.routeTo(me.getPosition(), destination).size()
                    + "\n");

            /** If we can use special rule */
            if ((gameController.getBoard().getGameType() == 1 && roll == 12 && destination instanceof Room)) {
                chosenRoute = new Vector();
                chosenRoute.add(me.getPosition());
                chosenRoute.add(destination);
                wholePath = true;
            }
            /** Can take a legal route in one go? ? */
            else if ((legalRoute.size() <= roll) && legalRoute.size() > 0) {
                chosenRoute = legalRoute;
                wholePath = true;
            }
            /** If we only move part of the way along */
            else {
                int movesOnLegalPath = 0;
                int movesOnIllegalPath = 0;

                if (legalRoute.size() > 0) {
                    movesOnLegalPath = validatePath(legalRoute, roll);
                    System.err.println("\nMY PLACE = " + movesOnLegalPath + "\n");
                }

                movesOnIllegalPath = validatePath(illegalRoute, roll);

                try {
                    System.err.println("\tmovesOnLegalPath " + movesOnLegalPath
                            + " movesOnIllegalPath " + movesOnIllegalPath);
                } catch (Exception e) {
                    e.printStackTrace();
                }

                if (movesOnLegalPath >= movesOnIllegalPath) {
                    chosenRoute = legalRoute;
                    movesOnPath = movesOnLegalPath;
                    System.err.println("LegalRoute " + movesOnPath);
                }
                else {
                    chosenRoute = illegalRoute;
                    // "Chosen: " + chosenRoute + " illegal: " + illegalRoute);
                    movesOnPath = movesOnIllegalPath - 1;
                    System.err.println("IllegalRoute " + movesOnPath);
                }

            }

            System.out.println("Chosen route size: " + chosenRoute.size());

            Position stopPoint = null;

            if (wholePath == true) {
                stopPoint = ((Position) chosenRoute.lastElement());
            }
            else {
                System.out.println(movesOnPath + " route: " + chosenRoute);
                stopPoint = ((Position) chosenRoute.get(movesOnPath - 1));
            }

            System.err.println("Moving to " + stopPoint.getID() + " " + chosenRoute + " wholePath "
                    + wholePath + " m=" + movesOnPath);

            if (movesOnPath >= 1 || wholePath == true) {
                movePlayer(stopPoint, chosenRoute);
            }
            else {
                System.err.println("AI Confused");
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * @param chosenRoute
     * @return
     */
    private int validatePath(Vector chosenRoute, int rollAllowed) {
        int movesOnPath = 1;

        for (int i = 1; i < chosenRoute.size(); i++) {
            // Can't pass through positions
            if (((Position) chosenRoute.get(i)).isOccupied() == true &&
                    ((Position) chosenRoute.get(i)) instanceof Square) {
                System.err.println("Break 1");
                break;
            }

            // Stop in a room
            if (((Position) chosenRoute.get(i)) instanceof Room) {
                System.err.println("Break 2 " + ((Position) chosenRoute.get(i)).getID() + " "
                        + movesOnPath);
                movesOnPath++;
                break;
            }

            // If the dice roll has been exceeded
            if (i >= rollAllowed) {
                System.err.println("Break 3");
                break;
            }

            System.err.println("movesOnPath: " + movesOnPath);
            movesOnPath++;
        }
        return movesOnPath;
    }

    /**
     * Finds a room for the AI to go to.
     * <p>
     * <li>- It goes to all uneliminated rooms based on which is the closest
     * 
     * @return a chosen destination
     */
    synchronized public Position findRoom() {
        Position destination = null;
        try {

            Player me = gameController.getBoard().getPlayer(myName);
            CardCollection myDCards = me.getDetectivePad().getCardList();

            /**
             * Find all rooms in the game.
             */
            Vector rooms = gameController.getBoard().map.getNodes("rooms");

            /**
             * Rooms to search
             */
            Vector roomsToSearch = new Vector();

            for (int i = 0; i < rooms.size(); i++) {
                if (myDCards.cardByName(((Room) rooms.get(i)).getID()).isEliminated() == false) {
                    roomsToSearch.add(rooms.get(i));
                }
            }

            int distances[] = findDistances(roomsToSearch);
            int smallest = Integer.MAX_VALUE;
            int smallestIndex = -1;

            for (int i = 0; i < distances.length; i++) {
                if (distances[i] <= smallest && distances[i] > 0) {
                    smallest = distances[i];
                    smallestIndex = i;
                }
            }

            StringBuffer latency = new StringBuffer();
            for (int i = 0; i < roomsToSearch.size(); i++) {
                latency.append(
                        ((Room) roomsToSearch.get(i)).getID() + " =  "
                                + roomsToSearch.get(i).getClass().getName() + "  (" + distances[i]
                                + ")\n");
            }
            outputMessage(latency.toString());

            if (smallestIndex == -1)
                destination = (Room) roomsToSearch.get(0);
            else
                destination = (Room) roomsToSearch.get(smallestIndex);

            try {
                System.out.println("My aim is: "
                        + destination.getID()
                        + "\n"
                        +
                        "Illegal size: "
                        + smallest
                        + "\n"
                        +
                        "Legal size: "
                        + gameController.getBoard().map.routeTo(me.getPosition(), destination)
                                .size() + "\n");
            } catch (Exception e) {
                e.printStackTrace();

            }
            if (gameController.getBoard().map.routeTo(me.getPosition(), destination).size() == 0
                    &&
                    gameController.getBoard().map.illegalRouteTo(me.getPosition(), destination)
                            .size() == 0) {
                System.out.println("AI not allowed to move.");
                return null;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return destination;
    }

    /**
     * @param roomsToSearch
     * @return
     */
    private int[] findDistances(Vector roomsToSearch) {
        int rooms[] = new int[roomsToSearch.size()];

        Position src = gameController.getBoard().getCurrentPlayer().getPosition();
        Map map = gameController.getBoard().map;

        for (int i = 0; i < roomsToSearch.size(); i++) {
            int path = map.illegalRouteTo(src, (Position) roomsToSearch.get(i)).size();
            rooms[i] = path;
        }

        return rooms;
    }

    /**
     * Finds if the AI should make an accusation.
     * 
     * Makes an accusation if there is 1 weapon, suspect, and room left uneliminated.
     * 
     * This method can be bypassed if a suggestion is not disproved and the player has none of the
     * cards, this means inherently that the player has found the secret cards.
     * 
     * @return accusation decision
     */
    synchronized public boolean shouldAccuse() {
        boolean result = false;

        Player me = gameController.getBoard().getPlayer(myName);
        CardCollection myDCards = me.getDetectivePad().getCardList();

        int numberOfWeapons = 0;
        int numberOfRooms = 0;
        int numberOfSuspects = 0;

        ArrayList cards = new ArrayList();

        for (int i = 0; i < myDCards.countCards(); i++) {
            if (myDCards.cardAt(i).isEliminated() == false) {
                if (myDCards.cardAt(i).getType().equals("Weapon"))
                    numberOfWeapons++;
                else if (myDCards.cardAt(i).getType().equals("Suspect"))
                    numberOfSuspects++;
                else if (myDCards.cardAt(i).getType().equals("Room"))
                    numberOfRooms++;

                cards.add(myDCards.cardAt(i));
            }
        }

        System.out.println("\n\n\n\nshouldAccuse: " + numberOfWeapons + " " + numberOfSuspects
                + " " + numberOfRooms + "\n\n\n\n");

        for (int i = 0; i < cards.size(); i++)
            System.out.println(((Card) cards.get(i)).getName());

        result = (numberOfWeapons == 1 && numberOfSuspects == 1 && numberOfRooms == 1);

        return result;
    }

    /**
     * Find 3 cards to use in an accusation then call the accuse() function.
     * 
     */
    synchronized public boolean makeAccusation() {
        Card weapon = null;
        Card suspect = null;
        Card room = null;

        Player me = gameController.getBoard().getPlayer(myName);
        CardCollection myDCards = me.getDetectivePad().getCardList();

        for (int i = 0; i < myDCards.countCards(); i++) {
            if (myDCards.cardAt(i).isEliminated() == false) {
                if (weapon == null && myDCards.cardAt(i).getType().equals("Weapon"))
                    weapon = myDCards.cardAt(i);
                else if (weapon == null && myDCards.cardAt(i).getType().equals("Suspect"))
                    suspect = myDCards.cardAt(i);
                else if (room == null && myDCards.cardAt(i).getType().equals("Room"))
                    room = myDCards.cardAt(i);
            }
        }

        return accuseCorrect(suspect, weapon, room);
    }

    /**
     * Check if an accusation is correct
     */
    synchronized public boolean accuseCorrect(Card suspectCard, Card weaponCard, Card roomCard) {
        boolean result = false;

        // Player currentPlayer = gameController.getBoard().getCurrentPlayer();

        System.out.println("accuseCorrect " + suspectCard.getName() + " " + weaponCard.getName()
                + " " + roomCard.getName());

        result = gameController.getBoard().getEnvelope().containsCard(weaponCard) == true &&
                gameController.getBoard().getEnvelope().containsCard(roomCard) == true &&
                gameController.getBoard().getEnvelope().containsCard(suspectCard) == true;

        try {
            System.out.println("accuseCorrect " + result + "\n envelope size "
                    + gameController.getBoard().getEnvelope().countCards() + " xml: "
                    + gameController.getBoard().getEnvelope().toXML());
        } catch (Exception e) {
            System.out.println("accuseCorrect " + result + "\n envelope  NULL ?! "
                    + gameController.getBoard().getEnvelope());
        }
        return result;
    }

    public String suggest() {
        Card suspect = null;
        Card weapon = null;
        String output = "";

        Player me = gameController.getBoard().getPlayer(myName);
        CardCollection myDCards = me.getDetectivePad().getCardList();

        boolean askDisproof = true;

        /** List of all suspects who haven't been eliminated yet **/
        Vector possSuspects = new Vector();
        /** List of all weapons which haven't been eliminated yet **/
        Vector possWeapons = new Vector();

        /**
         * Search through detective pad to find cards which aren't eliminated.
         */
        for (int i = 0; i < myDCards.countCards(); i++)
            if (myDCards.cardAt(i).getType().equals("Suspect")
                    && myDCards.cardAt(i).isEliminated() == false)
                possSuspects.add(myDCards.cardAt(i));
        /**
         * Search through detective pad to find cards which aren't eliminated.
         */
        for (int i = 0; i < myDCards.countCards(); i++)
            if (myDCards.cardAt(i).getType().equals("Weapon")
                    && myDCards.cardAt(i).isEliminated() == false)
                possWeapons.add(myDCards.cardAt(i));

        /**
         * Generate a random number then use that index to access the list of suspects.
         */
        int mod = possSuspects.size();
        if (mod != 0)
            suspect = (Card) possSuspects.elementAt((int) (Math.random() * possSuspects.size())
                    % possSuspects.size());
        else
            suspect = null;
        /**
         * Generate a random number then use that index to access the list of weapons.
         */
        mod = possWeapons.size();
        if (mod != 0)
            weapon = (Card) possWeapons.elementAt((int) (Math.random() * possWeapons.size())
                    % possWeapons.size());
        else
            weapon = null;

        /**
         * Get the room for the suggestion by the room the player is currently standing in.
         */
        Card room = gameController.getBoard().getCardPack().cardByName(me.getPosition().getID());

        Vector disproofInfo = findDisproveCandidate(suspect, weapon, room);

        Player playerCandidate = (Player) disproofInfo.firstElement();

        if (possSuspects != null) {
            output += "- suspects " + possSuspects.size() + " weapons: " + possWeapons.size()
                    + " pos " + me.getPosition().getID() + ".\n";
        }
        if (suspect != null) {
            output += "- I suspect " + suspect.getName();
        } else {
            output += "- I suspect " + "NULL";
        }
        if (weapon != null) {
            output += " of using the " + weapon.getName();
        } else {
            output += " of using the " + "NULL";
        }

        output += " in the " + me.getPosition().getID() + ".\n";
        output += "Candidate found: " + playerCandidate;
        outputMessage(output);

        if (suspect != null)
            moveDefendant(suspect);

        // Send out an alert for the suggestion

        if (playerCandidate != null) {
            disproofReceived = false;
            Message suggestionAlertMessage = new Message();
            Vector params = new Vector();
            // params.add( new SuggestionAlert(suspect, weapon, room, me.getRealName(),
            // playerCandidate.getRealName()) );
            params.add(new SuggestionAlert(suspect, weapon, room, me.getRealName()));
            suggestionAlertMessage.parameters = params;
            suggestionAlertMessage.type = "suggestionAlert";
            suggestionAlertMessage.data = playerCandidate.getRealName();

            gameController.relayMessage(suggestionAlertMessage);
        }
        else {
            outputMessage("Nobody could disprove");
            outputMessage(" i have weap? " + myDCards.containsCard(weapon) + "\n i have room? "
                    + myDCards.containsCard(room) + "\n i have suspect? "
                    + myDCards.containsCard(suspect));
            disproofReceived = false;

            askDisproof = false;
        }

        String cardShown = "";

        if (askDisproof == true) {
            // Wait for a reply from the suggestion
            while (disproofReceived == false) {
                outputMessage("# In waiting loop");
                sleepFor(100);
            }
            outputMessage("# Exit waiting loop");
            outputMessage("Start disproof decomposition");

            DisproofAlert disproofAlert = (DisproofAlert) incomingDisproof.parameters
                    .firstElement();
            // SuggestionAlert originalSuggestionAlert = disproofAlert.getOriginalSuggestion();

            cardShown = disproofAlert.getCardShown();

            System.out.println(" - Card shown: " + cardShown);
        }
        else {
            cardShown = "";
        }

        // Figure out what to do after the suggestion

        /** A card was shown to us */
        if (!cardShown.equals("")) {
            outputMessage("I was shown a card for my suggestion, " + cardShown);

            // We got a good response from the suggestion, one card has been
            // eliminated.
            eliminateCard(myDCards.cardByName(cardShown));
            System.out.println("Card marked");
        }

        /** No card was shown to us */
        else {

            if (me.getCards().containsCard(suspect) == false
                    && me.getCards().containsCard(weapon) == false
                    && me.getCards().containsCard(room) == false) {

                output += "\nI secretly won because I know nobody else has the cards.\n";
                System.out.println(output);

                // At this stage we should check if we have the cards.. if not..
                // then we must know that we've found the correct cards.
                if (accuseCorrect(suspect, weapon, room) == true) {
                    outputMessage("The accusation made was correct");
                    Vector parameters = new Vector();
                    parameters.add(me);
                    gameController.directCommand("winGame", parameters);
                    // dead = true;
                } else {
                    outputMessage("The accusation made was WRONG..!");
                    // dead = true;
                }
            } else {
                output += "Unable to disprove suggestion because I had all the cards myself.";
            }
        }

        /** So we can't suggest next turn */
        markSuggestionMade();
        return output;
    }

    public void markSuggestionMade() {
        Vector param = new Vector();
        param = new Vector();
        param.add(gameController.getBoard().getPlayer(myName));
        gameController.directCommand("markSuggestMade", param);
    }

    public void eliminateCard(Card shown) {
        Vector param = new Vector();
        param.add(shown.getName());
        gameController.directCommand("eliminateCard", param);
    }

    /**
     * Performs the move of the player from current position to "target".
     * 
     */
    synchronized public void movePlayer(Position target, Vector path) {
        String output = "";
        Vector pathTaken = applyConstraint(target, path);
        System.out.println("Feed forward : " + pathTaken);

        Player me = gameController.getBoard().getPlayer(myName);
        output = "#movePlayer Moving to " + target.getID();
        me.setMovesRemaining(0);
        Vector param = new Vector();
        param.add(target);
        param.add(me);
        param.add(pathTaken);
        gameController.directCommand("movePlayer", param);

        outputMessage(output);
    }

    /**
     * 
     * Applies constraints to the path
     * 
     * @param target
     * @param path
     * @return a new path with the constraints applied
     */
    private Vector applyConstraint(Position target, Vector path) {
        Vector altPath = new Vector();
        for (int i = 0; i < path.size(); i++) {
            if (path.elementAt(i) == target) {
                altPath.add(path.elementAt(i));
                break;
            }
            else {
                altPath.add(path.elementAt(i));
            }
        }
        return altPath;
    }

    /**
     * Rolls the dice and then gets the result.
     * 
     * @return number of both dice
     */
    public int rollDice() throws Exception {
        int result = 0;

        Player me = gameController.getBoard().getPlayer(myName);
        System.out.println("Sending command");
        gameController.directCommand("roll", new Vector());

        System.out.println("Command sent");
        result = me.getMovesRemaining();
        System.out.println("Result: " + result);

        return result;
    }

    /**
     * Find a player for a disproof
     * <p>
     *
     * Returns Vector
     * <p>
     * <li>- first element -- pointer to player with cards
     * <li>- second element -- Vector of cards for disprove
     * <li>- elements -- each Card
     */
    public Vector findDisproveCandidate(Card who, Card what, Card where) {
        Vector results = new Vector();

        String output = "";
        Player playerWithCard = null;
        Vector cardsFound = new Vector();

        Player currentPlayer = gameController.getBoard().getCurrentPlayer();

        // Call who into where.

        Player defendant = gameController.getBoard().getCharacter(who.getName());

        if (!currentPlayer.getCharacter().equals(who.getName())) {
            if (defendant != null) {
                // Position oldRoom = defendant.getPosition();
                Position destination = currentPlayer.getPosition();

                Vector parameters = new Vector();
                parameters.add(destination);
                parameters.add(defendant); // Who to move..
                gameController.directCommand("movePlayer", parameters);
            }
        }

        // Search all players for a card.

        playerWithCard = null;
        cardsFound = new Vector();

        // Order the players so we can scan through and check who has cards in the right order.
        Vector playersToParse = localArrangePlayersBy(currentPlayer);

        for (int i = 0; i < playersToParse.size(); i++) {
            playerWithCard = (Player) playersToParse.elementAt(i);
            if (playerWithCard != currentPlayer) {
                if (playerWithCard.hasCard(who))
                    cardsFound.add(who);
                if (playerWithCard.hasCard(what))
                    cardsFound.add(what);
                if (playerWithCard.hasCard(where))
                    cardsFound.add(where);
                if (cardsFound.size() > 0)
                    break;
            }
        }

        if (cardsFound.size() > 0) {
            output += playerWithCard.getRealName()
                    + " choose which card you will use to disprove :-\n ";
            for (int i = 0; i < cardsFound.size(); i++) {
                output += ((Card) cardsFound.elementAt(i)).getName() + " ";
            }
            // gameConfig.setDisproveMode(true);
        }
        else {
            output += "Unable to disprove the suggestion.";
            playerWithCard = null;
        }

        outputMessage(output);

        results.add(playerWithCard);
        results.add(cardsFound);

        return results;
    }

    /**
     * Order the players so we can scan through and check who has cards in the right order.
     * 
     */
    private Vector localArrangePlayersBy(Player currentPlayer) {
        Vector players = new Vector();
        int playerPosition = gameController.getBoard().getPlayerIndex(currentPlayer);
        Vector livePlayers = gameController.getBoard().getPlayers();

        // Add all elements after the player
        for (int i = playerPosition + 1; i < livePlayers.size(); i++)
            players.add(livePlayers.elementAt(i));
        // Add all elements before the player
        if (!(playerPosition == 0))
            for (int i = 0; i < playerPosition; i++)
                players.add(livePlayers.elementAt(i));
        return players;

    }

    /**
     * Checks if a suggest was disproved or not with a given suspect and weapon.
     * 
     * @parameter suspect the suspect to eliminate (who did it)
     * @parameter suspect the weapon to eliminate (what did it)
     * 
     * @return false - unable to disprove, true - disproved.
     */
    public void moveDefendant(Card suspect) {
        Player defendant = gameController.getBoard().getCharacter(suspect.getName());
        Player me = gameController.getBoard().getPlayer(myName);

        Card room = gameController.getBoard().getCardPack().cardByName(me.getPosition().getID());
        if (defendant != null) {
            String output = "";

            if (defendant != null) {
                // Position oldRoom = defendant.getPosition();
                Position destination = me.getPosition();

                Vector parameters = new Vector();
                parameters.add(destination); // Where to move
                parameters.add(defendant); // Who to move there
                gameController.directCommand("movePlayer", parameters);
            }

            output = "Called " + defendant.getRealName() + " into the " + room.getName();

            outputMessage(output);
        }
    }

    public boolean inDeadlock() {
        Position myPosition = gameController.getBoard().getCurrentPlayer().getPosition();
        Vector N = myPosition.getNeighbours();
        int free = 0;

        for (int i = 0; i < N.size(); i++) {
            Position n = (Position) (N.get(i));
            if (n instanceof Square || n instanceof StartingSquare) {
                if (n.isOccupied() == false) {
                    free++;
                }
            }
            else {
                free++;
            }
        }
        return (free == 0);
    }

    /*
     * @see Cluedo.API.AIContract#movePlayer(Cluedo.Game.Position, java.util.Vector, int)
     */
    public void movePlayer(Position target, Vector path, int amount) {

    }

    private void updatePlayerSoftEvidence(DisproofAlert disproofAlert,
            Player playerCandidate,
            Card suspect, Card weapon, Card room) {
        cDataPad.updatePlayerSoftEvidence(
                playerMapping.get(myName),
                playerMapping.get(playerCandidate.getRealName()),
                new String[] {
                        suspect.getName(), weapon.getName(), room.getName()
                });
        updateRegistryData(me);

    }

    private void updatePlayer(DisproofAlert disproofAlert) {
        cDataPad.updatePlayer(
                playerMapping.get(myName),
                playerMapping.get(disproofAlert.disprovedBy),
                new String[] {
                        disproofAlert.originalSuggestionAlert.who.getName(),
                        disproofAlert.originalSuggestionAlert.what.getName(),
                        disproofAlert.originalSuggestionAlert.where.getName()
                },
                disproofAlert.cardShown);
        updateRegistryData(me);
    }

    private void updateRegistryData(Player me) {
        for (int i = 0; i <= CluedoDataPad.getTokens().size(); i++) {
            Card card = me.getDetectivePad().getCardList().cardAt(i);
            if (card != null) {
                int tokCol = CluedoDataPad.getTokens().get(card.getName());
                card.setProbability(cDataPad.getGrid()[cDataPad.getGrid().length - 1][tokCol]);
            }
        }
    }
}
